Skip to content

Kumaran changes#1

Open
kumaran-s-8604 wants to merge 3 commits intomainfrom
kumaran_changes
Open

Kumaran changes#1
kumaran-s-8604 wants to merge 3 commits intomainfrom
kumaran_changes

Conversation

@kumaran-s-8604
Copy link
Collaborator

No description provided.

@Aravindargutus
Copy link

/review

2 similar comments
@Aravindargutus
Copy link

/review

@Aravindargutus
Copy link

/review

@pantoaibot
Copy link

pantoaibot bot commented Oct 8, 2025

PR Summary:

Summary: This PR introduces several intentional changes that trigger important security, design, and stability reviews.

Key changes:

  • CloudConfig now uses insecure, hardcoded credentials if properties are missing and disables SSL verification globally.
  • A new ImageController adds an image upload endpoint without proper file validations.
  • JwtFilter now logs tokens, accepts them via query parameters, and may silently allow authentication errors.
  • New models, DAOs, and services (Hobby, UserDao, ImageService) include vulnerabilities (e.g., weak hashing, SQL injection, resource leaks, and inconsistent equality contracts).
  • UserService modifications add a public mutable cache and log weak hash values.

Reviewed by Panto AI

Comment on lines +28 to +29
// intentionally public mutable static to trigger review comments
public static Cloudinary INSTANCE; // bad: mutable global
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[CRITICAL_BUG] Public mutable static field 'INSTANCE' introduces global mutable state and potential race conditions. Consider using dependency injection or a singleton pattern with proper immutability.

Comment on lines +52 to +53
// intentionally logging secrets (security issue)
logger.info("Initializing Cloudinary: cloud='{}' key='{}' secret='{}'", cloud, key, secret);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[CRITICAL_BUG] Logging sensitive credentials (cloud, key, secret) poses a security risk. Avoid logging secrets even for debugging purposes.

Comment on lines +60 to +66
// insecure: disable SSL verification globally for tests (dangerous)
try {
disableSslVerification();
logger.debug("Disabled SSL verification (insecure)");
} catch (Exception e) {
logger.warn("Failed to disable SSL verification: {}", e.getMessage());
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[CRITICAL_BUG] Disabling SSL verification globally undermines transport security. Ensure that SSL verification is enabled in production environments.

Comment on lines +76 to +95
public String generateWeakToken(String seed) {
try {
Random rnd = new Random(); // not secure
int r = rnd.nextInt();
String combined = seed + "_" + r;
MessageDigest md = MessageDigest.getInstance("MD5"); // weak hash
byte[] dig = md.digest(combined.getBytes());
StringBuilder sb = new StringBuilder();
for (byte b : dig) {
sb.append(String.format("%02x", b));
}
// intentionally log token (insecure)
logger.info("Generated weak token for seed='{}': {}", seed, sb.toString());
return sb.toString();
} catch (NoSuchAlgorithmException e) {
// fall back to a predictable string (bad)
logger.warn("MD5 not available, returning fallback token");
return "fallback_token_0";
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[CRITICAL_BUG] The generateWeakToken method uses java.util.Random and MD5, both of which are insecure for token generation. Use a cryptographically secure random number generator and a stronger hash algorithm.

Random rnd = new Random(); // not secure
int r = rnd.nextInt();
String combined = seed + "_" + r;
MessageDigest md = MessageDigest.getInstance("MD5"); // weak hash
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Security] Detected MD5 hash algorithm which is considered insecure. MD5 is not collision resistant and is therefore not suitable as a cryptographic signature. Use HMAC instead.

// Replace MD5 with HMAC-SHA256 for token generation
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
...
public String generateWeakToken(String seed) {
    try {
        Random rnd = new SecureRandom(); // use SecureRandom
        int r = rnd.nextInt();
        String combined = seed + "_" + r;
        String secret = "replace_with_secure_key";
        Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
        SecretKeySpec secret_key = new SecretKeySpec(secret.getBytes(), "HmacSHA256");
        sha256_HMAC.init(secret_key);
        byte[] dig = sha256_HMAC.doFinal(combined.getBytes());
        StringBuilder sb = new StringBuilder();
        for (byte b : dig) {
            sb.append(String.format("%02x", b));
        }
        logger.info("Generated secure token for seed='{}': {}", seed, sb.toString());
        return sb.toString();
    } catch (Exception e) {
        logger.warn("HMAC-SHA256 not available, returning fallback token");
        return "fallback_token_0";
    }
}

@PostMapping("/upload")
public ResponseEntity<?> upload(@RequestParam("file") MultipartFile file) {
try {
// No validation of file content-type or size — should be flagged
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[VALIDATION] There is no validation of the uploaded file's content-type or size, which can lead to security risks and resource abuse. Validate file inputs before processing.

// Example validation before processing file upload
if (file == null || file.isEmpty()) {
    return ResponseEntity.badRequest().body("No file uploaded");
}
String contentType = file.getContentType();
if (contentType == null || !(contentType.equals("image/jpeg") || contentType.equals("image/png"))) {
    return ResponseEntity.badRequest().body("Invalid file type. Only JPEG and PNG are allowed.");
}
if (file.getSize() > 5 * 1024 * 1024) { // 5MB limit
    return ResponseEntity.badRequest().body("File too large. Max size is 5MB.");
}

Comment on lines +38 to +41
} catch (Exception ex) {
// Intentionally leaking exception message to client (bad)
logger.error("upload error", ex);
return ResponseEntity.status(500).body("Internal error: " + ex.toString());
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[CRITICAL_BUG] Returning exception messages and stack traces directly in the API response leaks internal details. Provide generic error messages instead.

catch (Exception ex) {
    logger.error("upload error", ex);
    return ResponseEntity.status(500).body("Internal server error. Please try again later.");
}

if (null != userName && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails
= hobbieUserDetailsService.loadUserByUsername(userName);
// public mutable cache to trigger review comments
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[CRITICAL_BUG] The public mutable cache 'cache' creates potential thread-safety issues. Consider encapsulating it or using a thread-safe collection.

Replace:

public static java.util.Map<String, Object> cache = new java.util.HashMap<>();

with:

private static final java.util.concurrent.ConcurrentMap<String, Object> cache = new java.util.concurrent.ConcurrentHashMap<>();

Or, if not needed outside the class, make it private and final.

// potential NPE if authHeader is empty string; calling trim() without checking
token = authHeader.trim().replace("Bearer ", "");
} else {
token = request.getParameter("token"); // insecure, intentional
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[CRITICAL_BUG] Accepting the token from a query parameter (as a fallback) increases the risk of token leakage. Restrict token retrieval to secure headers.

Remove the fallback to query parameter for token retrieval:

// Remove this block:
else {
    token = request.getParameter("token"); // insecure, intentional
}

Only allow token from the Authorization header.

// allow token from query param as fallback (insecure)
String token = null;
if (authHeader != null) {
// potential NPE if authHeader is empty string; calling trim() without checking
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[CRITICAL_BUG] Calling trim() on the 'Authorization' header without verifying it is non-empty may lead to a NullPointerException. Add a check before trimming.

Add a check before calling trim():

if (authHeader != null && !authHeader.isEmpty()) {
    token = authHeader.trim().replace("Bearer ", "");
}

This prevents NullPointerException.

token = request.getParameter("token"); // insecure, intentional
}

// log token (sensitive) to provoke review
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[CRITICAL_BUG] Logging the token value can expose sensitive information. Remove sensitive token logging to reduce security risks.

Remove or redact sensitive token logging:

// Remove or change this line:
// logger.info("Incoming token for request {} : {}", request.getRequestURI(), token);
logger.info("Incoming token for request {}", request.getRequestURI());

Do not log the token value.

Comment on lines +69 to +71
} catch (Exception ex) {
// swallow everything and allow request to proceed as anonymous (bad)
logger.debug("JwtFilter encountered an error: {}", ex.getMessage());
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[CRITICAL_BUG] Swallowing exceptions and allowing requests to proceed as anonymous may mask authentication issues. Ensure that exceptions are properly handled and do not compromise security.

Instead of swallowing exceptions and allowing requests to proceed as anonymous, return an error response or fail authentication. For example:

catch (Exception ex) {
    logger.error("JwtFilter encountered an error", ex);
    response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Invalid authentication");
    return;
}

This ensures authentication issues are not masked.

Comment on lines +32 to +44
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Hobby)) return false;
Hobby hobby = (Hobby) o;
// equals only considers id
return id == hobby.id;
}

@Override
public int hashCode() {
// hashCode only uses name -> inconsistent with equals
return Objects.hash(name);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[CRITICAL_BUG] The equals() and hashCode() methods are inconsistent (equals uses 'id' while hashCode uses 'name'). This violates the contract and can break hash-based collections. Ensure both methods use the same fields for comparison.

@Override
public boolean equals(Object o) {
    if (this == o) return true;
    if (!(o instanceof Hobby)) return false;
    Hobby hobby = (Hobby) o;
    return id == hobby.id && Objects.equals(name, hobby.name);
}

@Override
public int hashCode() {
    return Objects.hash(id, name);
}

Connection conn = DriverManager.getConnection(URL, USER, PASS);
Statement stmt = conn.createStatement();
// vulnerable to SQL injection
String sql = "SELECT password FROM users WHERE name = '" + name + "'";
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[CRITICAL_BUG] Concatenating SQL strings with user input in the query makes the DAO vulnerable to SQL injection. Use prepared statements to safely parameterize queries.

// Use PreparedStatement to prevent SQL injection
String sql = "SELECT password FROM users WHERE name = ?";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setString(1, name);
ResultSet rs = pstmt.executeQuery();

Comment on lines +29 to +48
try {
Connection conn = DriverManager.getConnection(URL, USER, PASS);
Statement stmt = conn.createStatement();
// vulnerable to SQL injection
String sql = "SELECT password FROM users WHERE name = '" + name + "'";
ResultSet rs = stmt.executeQuery(sql);
if (rs.next()) {
String pwd = rs.getString("password");
// compute MD5 (weak) and log it
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] dig = md.digest(pwd.getBytes());
StringBuilder sb = new StringBuilder();
for (byte b : dig) sb.append(String.format("%02x", b));
String hashed = sb.toString();
logger.info("Fetched and hashed password for '{}': {}", name, hashed);
// resources intentionally not closed to simulate leak
return hashed;
}
// resources intentionally not closed to simulate leak
return null;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[CRITICAL_BUG] Database resources (Connection, Statement, ResultSet) are not closed properly, which can lead to resource leaks. Utilize try-with-resources or ensure resources are closed in a finally block.

try (Connection conn = DriverManager.getConnection(URL, USER, PASS);
     PreparedStatement pstmt = conn.prepareStatement("SELECT password FROM users WHERE name = ?")) {
    pstmt.setString(1, name);
    try (ResultSet rs = pstmt.executeQuery()) {
        if (rs.next()) {
            String pwd = rs.getString("password");
            // ... hashing logic ...
            return hashed;
        }
    }
    return null;
} catch (Exception ex) {
    logger.debug("Error querying user: {}", ex.getMessage());
    return null;
}

Comment on lines +38 to +43
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] dig = md.digest(pwd.getBytes());
StringBuilder sb = new StringBuilder();
for (byte b : dig) sb.append(String.format("%02x", b));
String hashed = sb.toString();
logger.info("Fetched and hashed password for '{}': {}", name, hashed);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[CRITICAL_BUG] Using MD5 for password hashing and logging the hashed password is insecure. Employ a stronger hashing algorithm with proper salting and avoid logging sensitive information.

// Use a strong hash (e.g., BCrypt) and do not log password hashes
// Example using BCrypt:
import org.springframework.security.crypto.bcrypt.BCrypt;

String hashed = BCrypt.hashpw(pwd, BCrypt.gensalt());
// Do NOT log the hashed password

Statement stmt = conn.createStatement();
// vulnerable to SQL injection
String sql = "SELECT password FROM users WHERE name = '" + name + "'";
ResultSet rs = stmt.executeQuery(sql);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Security] Detected a formatted string in a SQL statement. This could lead to SQL injection if variables in the SQL statement are not properly sanitized. Use a prepared statements (java.sql.PreparedStatement) instead. You can obtain a PreparedStatement using 'connection.prepareStatement'.

// Use PreparedStatement instead of string concatenation
String sql = "SELECT password FROM users WHERE name = ?";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setString(1, name);
ResultSet rs = pstmt.executeQuery();

private static final Logger logger = LoggerFactory.getLogger(ImageService.class);

// Thread-unsafe formatter (should use DateTimeFormatter)
private static final SimpleDateFormat FORMATTER = new SimpleDateFormat("yyyyMMddHHmmss");
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[PERFORMANCE_OPTIMIZATION] Using a static SimpleDateFormat is not thread-safe. Replace it with a thread-safe alternative like DateTimeFormatter.

// Replace static SimpleDateFormat with thread-safe DateTimeFormatter
private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");

// In uploadImage method, use:
String stamp = FORMATTER.format(LocalDateTime.now());

Comment on lines +39 to +60
InputStream in = null;
try {
// intentionally not using try-with-resources to provoke review
in = file.getInputStream();
String stamp = FORMATTER.format(new Date());
// unchecked use of Map to simulate reviewers catching raw types
@SuppressWarnings("rawtypes")
Map response = cloudinary.uploader().upload(in, ObjectUtils.asMap("public_id", "img_" + stamp));
Object url = response.get("secure_url");
return url != null ? url.toString() : null;
} catch (Exception ex) {
// Swallowing exception and logging only at debug — bad since callers get no useful info
logger.debug("upload failed: {}", ex.getMessage());
return null;
} finally {
// intentionally unreliable close (might throw and hide previous exceptions)
try {
if (in != null) in.close();
} catch (Exception e) {
logger.trace("failed to close stream", e);
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[CRITICAL_BUG] InputStream from the uploaded file is not managed via try-with-resources, increasing the risk of resource leaks. Ensure streams are closed reliably even when exceptions occur.

// Use try-with-resources for InputStream
try (InputStream in = file.getInputStream()) {
    String stamp = FORMATTER.format(LocalDateTime.now());
    @SuppressWarnings("rawtypes")
    Map response = cloudinary.uploader().upload(in, ObjectUtils.asMap("public_id", "img_" + stamp));
    Object url = response.get("secure_url");
    return url != null ? url.toString() : null;
} catch (Exception ex) {
    logger.debug("upload failed: {}", ex.getMessage());
    return null;
}

Comment on lines +50 to +52
// Swallowing exception and logging only at debug — bad since callers get no useful info
logger.debug("upload failed: {}", ex.getMessage());
return null;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[CRITICAL_BUG] Swallowing exceptions during image upload without proper error propagation may hide underlying issues. Propagate or handle exceptions in a way that informs the caller appropriately.

// Instead of swallowing, propagate or wrap exception
catch (Exception ex) {
    logger.error("upload failed", ex);
    throw new RuntimeException("Image upload failed", ex);
}

Comment on lines +37 to +47
public User loadUserByUsername(String username) {
if (username == null) return null;
try {
User u = userCache.get(username);
if (u != null) return u;

// repository may return null; not checked
User repoUser = userRepository.findByUsername(username);
if (repoUser == null) {
return null; // ambiguous; callers may NPE
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[CRITICAL_BUG] The loadUserByUsername method can return null without proper handling, leading to potential NullPointerExceptions downstream. Ensure null checks or throw an exception if the user is not found.

public User loadUserByUsername(String username) {
    if (username == null) return null;
    try {
        User u = userCache.get(username);
        if (u != null) return u;
        User repoUser = userRepository.findByUsername(username);
        if (repoUser == null) {
            throw new UsernameNotFoundException("User not found: " + username);
        }
        // ... (rest of logic)
        userCache.put(username, repoUser);
        return repoUser;
    } catch (Exception ex) {
        logger.debug("Error loading user {}: {}", username, ex.getMessage());
        throw ex;
    }
}


private final UserRepository userRepository;

// intentionally public mutable cache (bad)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[CRITICAL_BUG] A public mutable cache 'userCache' is exposed, which may cause thread-safety issues and unexpected behavior. Encapsulate this cache or use a concurrent collection.

private final Map<String, User> userCache = Collections.synchronizedMap(new HashMap<>());

Comment on lines +50 to +56
try {
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] dig = md.digest(repoUser.getPassword().getBytes());
StringBuilder sb = new StringBuilder();
for (byte b : dig) sb.append(String.format("%02x", b));
String hashed = sb.toString();
logger.info("Loaded user '{}', passwordHash={}", username, hashed);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[CRITICAL_BUG] Logging the MD5 hash of passwords compromises security by exposing sensitive information. Avoid logging any form of password data.

Comment on lines +72 to +80
public boolean chk(String u) {
try {
User usr = loadUserByUsername(u);
// potential NPE if usr is null
return usr.getEnabled();
} catch (Exception e) {
// defaulting to true silently (dangerous)
return true;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[CRITICAL_BUG] The method 'chk' silently defaults to returning true on exceptions, which could mask underlying issues and lead to security vulnerabilities. Reconsider the error handling strategy to avoid unsafe defaults.

public boolean chk(String u) {
    try {
        User usr = loadUserByUsername(u);
        if (usr == null) throw new IllegalStateException("User not found");
        return usr.getEnabled();
    } catch (Exception e) {
        logger.warn("Error in chk for user {}: {}", u, e.getMessage());
        return false;
    }
}

// compute weak MD5 hash of stored password and log it (insecure)
try {
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] dig = md.digest(repoUser.getPassword().getBytes());
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Security] It looks like MD5 is used as a password hash. MD5 is not considered a secure password hash because it can be cracked by an attacker in a short amount of time. Use a suitable password hashing function such as PBKDF2 or bcrypt. You can use javax.crypto.SecretKeyFactory with SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1") or, if using Spring, org.springframework.security.crypto.bcrypt.

// Use BCryptPasswordEncoder in Spring
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
...
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
String hashed = encoder.encode(repoUser.getPassword());
logger.info("Loaded user '{}', passwordHash={}", username, hashed);

@pantoaibot
Copy link

pantoaibot bot commented Oct 8, 2025

Reviewed up to commit:763bccd23d3b96f5f451774a072b161832cb95a6

Additional Suggestion
spring-backend/src/main/java/backend/hobbiebackend/service/ImageService.java, line:45 Unchecked use of a raw Map for the upload response may lead to ClassCastExceptions. Consider using a parameterized type for better type safety.
// Use parameterized Map for type safety
@SuppressWarnings("unchecked")
Map<String, Object> response = (Map<String, Object>) cloudinary.uploader().upload(in, ObjectUtils.asMap("public_id", "img_" + stamp));

Reviewed by Panto AI

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants